package com.andy.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Activate(group = {CommonConstants.CONSUMER,CommonConstants.PROVIDER})
public class TPMonitorFilter implements Filter{

    private final Map<String, LinkedList<InvokeRecord>> methodCache = new ConcurrentHashMap<String, LinkedList<InvokeRecord>>();

    /**
     * Make sure call invoker.invoke() in your implementation.
     *
     * @param invoker
     * @param invocation
     */
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long beginTime = System.currentTimeMillis();
        try {
            return invoker.invoke(invocation);
        }catch (RpcException e){
            throw e;
        }finally {
            catchRecord(invocation,beginTime);
        }
    }

    /**
     * 缓存调用信息到链表中
     * @param invocation
     * @param beginTime
     */
    private void catchRecord(Invocation invocation,long beginTime){
        synchronized (methodCache){
            long endTime = System.currentTimeMillis();
            //没有缓存就创建新的列表
            String methodName = invocation.getMethodName();
            if(!methodCache.containsKey(methodName)){
                methodCache.put(methodName,new LinkedList <InvokeRecord>());
            }
            //拿到链表,放入本次的
            List <InvokeRecord> invokeRecords = methodCache.get(methodName);
            invokeRecords.add(new InvokeRecord(beginTime,endTime-beginTime));
        }
    }

    class InvokeRecord{
        private long beginTime;
        private long durationTime;

        public InvokeRecord(long beginTime, long durationTime) {
            this.beginTime = beginTime;
            this.durationTime = durationTime;
        }

        public long getBeginTime() {
            return beginTime;
        }

        public void setBeginTime(long beginTime) {
            this.beginTime = beginTime;
        }

        public long getDurationTime() {
            return durationTime;
        }

        public void setDurationTime(long durationTime) {
            this.durationTime = durationTime;
        }
    }

    public TPMonitorFilter() {
        startMonitor();
    }

    private void startMonitor(){
        new Thread(()->{
            while(true){
                //清空过期的数据
                clearExpireExport();
                //计算当前TP90,TP99
                calcAndPrintTP();
                //等5秒
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


    private void calcAndPrintTP(){
        System.out.println("打印监控【"+new Date());
        for (Map.Entry <String, LinkedList <InvokeRecord>> entryList : methodCache.entrySet()) {
            LinkedList <InvokeRecord> records = entryList.getValue();
            if(records==null||records.size()==0) continue;

            LinkedList <InvokeRecord> copyRecords = new LinkedList <>(records);
            copyRecords.sort(Comparator.comparing(InvokeRecord::getDurationTime));
            //计算TP90的index
            int size = copyRecords.size();
            int tp90Index = (int)(size * 0.9);
            int tp99Index = (int)(size * 0.99);
            System.out.println(entryList.getKey()
                    +"====>>>> TP90: "+copyRecords.get(tp90Index).getDurationTime()
                    +";TP99: "+copyRecords.get(tp99Index).getDurationTime());
        }
        System.out.println("打印完毕】");
        System.out.println();
        System.out.println();
    }

    private void clearExpireExport(){
        synchronized (methodCache){
            long now = System.currentTimeMillis();
            long expireTime = now - 1*60*1000;//只统计1分钟，1分钟前的就过期了
            for (Map.Entry <String, LinkedList <InvokeRecord>> entryList : methodCache.entrySet()) {
                LinkedList <InvokeRecord> records = entryList.getValue();
                while(records.size()>0){
                    if(records.getFirst().getBeginTime()<expireTime){
                        records.removeFirst();
                    }else{
                        break;
                    }
                }
            }
        }
    }
}
